﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.IO;
//using System.Dynamic;

namespace wsserver
{	
	class Map
	{
		public string name;
		public int width;
		public int height;
		
		public int[,] locality;
		public int[,] resources;
		public int[,] units;
		
		public Map(string val_name, int val_width, int val_height)
		{
			name = val_name;
			width = val_width;
			height = val_height;
			
			locality = new int[width, height];
			resources = new int[width, height];
			units = new int[width, height];
		}
	}
	
	// класс игры KoAbra
	class KoAbra
	{
		struct C_game_status
		{
			public static int NOT_STARTED = 0;
			public static int STARTED = 1;
			public static int PAUSED = 2;
			public static int STOPED = 3;
		}
			
		public string name = "";
		public int status = C_game_status.NOT_STARTED;
		
		public User creator;	// создатель
		public Map map;			// карта
		
		public List<User> Game_users
		{
			get
			{
				return _game_users;
			}
		}
		public List<User> _game_users;
		
		// конструктор игры KoAbra
		public KoAbra(User usr, string val_name)
		{
			creator = usr;
			name = val_name;
			_game_users = new List<User>();
		}
		
		struct C_errors
		{
			// перечень ошибок
		}
		
		public int Start()
		{
			// Куча проверок		
			
			// if(ошибка) return(C_errors.ошибка)
			// else
			status = C_game_status.STARTED;
			return 0;
		}
		
		public int Pause()
		{
			// Куча проверок		
			
			// if(ошибка) return(C_errors.ошибка)
			// else
			status = C_game_status.PAUSED;
			return 0;
		}
		
		public int Stop()
		{
			// Куча проверок			
			
			// if(ошибка) return(C_errors.ошибка)
			// else
			status = C_game_status.STOPED;
			return 0;
		}
	}
	
	class PackageResult
	{
		public string type = "Action";
		public string action = "";
		public string status;
		public string reason;
	}
	
	class PackageRemoveGame
	{
		public string type = "Action";
		public string action = "";
		public string name;
	}
	
	// класс формы авторизации/отсоединения в чате
	class PackageChatAuth
	{
		public string type = "ChatAuth";
		public string login;
	}
	
	// класс формы сообщения из чата
	class PackageChatMessage
	{
		public string type = "ChatMessage";
		public string login;
		public string message;
	}
	
	// класс формы списка доступных игр / игроков
	class PackageListNames
	{
		public string type = "Action";
		public string action = "Send_gamelist";
		public string[] names;
		
		public PackageListNames(int count)
		{
			names = new string[count];
		}
	}
	
    class Server
    {
        private TcpListener listener;	// Listens for connections from TCP network clients
        private List<Thread> activeThreads;

        // список активных игр
		public List<KoAbra> KoAbras
		{
			get
			{
				return _koabras;
			}
		}		
		private List<KoAbra> _koabras;
		
		//клиенты, что подключены к серверу
        public List<User> Users
        {
            get
            {
                return _users;
            }
        }

        private List<User> _users;


        //IP сервера
        public IPAddress IP
        {
            get
            {
                return _ip;
            }
        }

        private IPAddress _ip;

        public int Port
        {
            get
            {
                return _port;
            }
        }

        private int _port;

        public bool Log
        {
            get;
            set;
        }

        //конструктор сервера по умолчанию
        public Server()
        {
            _users = new List<User>();
			_koabras = new List<KoAbra>();
            _port = 8181;
            _ip = IPAddress.Parse("0.0.0.0");
            Log = true;
        }

        //констуктор с параметрами
        public Server(IPAddress ip, int port, bool log)
        {
            _users = new List<User>();
			_koabras = new List<KoAbra>();
            _port = port;
            _ip = ip;
            Log = log;
        }
		
        static byte[] BuildKey(string data)
        {
            int spaceCount = 0;
            string partialKey = "";
            char[] keyChars = data.ToCharArray();
            foreach (char c in keyChars)
            {
                if (char.IsDigit(c))
                    partialKey += c;
                if (char.IsWhiteSpace(c))
                    spaceCount++;
            }
            byte[] answ = BitConverter.GetBytes((int)(Int64.Parse(partialKey) / spaceCount));
            if (BitConverter.IsLittleEndian)
                Array.Reverse(answ);
            return answ;
        }


        private void Handshake(object obj)
        {
            var client = (TcpClient)obj;
            try
            {
                var stream = client.GetStream();
                byte[] data = new byte[1000];
                int read = 0;
                StringBuilder clientHandshakeBuilder = new StringBuilder();
                read = stream.Read(data, 0, 1000);
                //last 8 bytes - handshake secret
                clientHandshakeBuilder.Append(Encoding.ASCII.GetString(data, 0, read - 8));

                byte[] key1 = new byte[4], key2 = new byte[4];

                string[] handshakeLines = clientHandshakeBuilder.ToString().Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);

                string origin = "", location = "";

                foreach (string line in handshakeLines)
                {
                    LogIn(line);
                    if (line.StartsWith("Sec-WebSocket-Key1:"))
                        key1 = BuildKey(line.Substring(line.IndexOf(":") + 2));
                    if (line.StartsWith("Sec-WebSocket-Key2:"))
                        key2 = BuildKey(line.Substring(line.IndexOf(":") + 2));
                    if (line.StartsWith("Origin:"))
                        origin = line.Substring(line.IndexOf(":") + 2);
                    if (line.StartsWith("Host:"))
                        location = "ws://" + line.Substring(line.IndexOf(":") + 2) + "/test";
                }

                byte[] lastKey = new byte[8];
                Array.Copy(data, read - 8, lastKey, 0, 8);

                //All screts together
                byte[] all = new byte[16];
                Array.Copy(key1, all, 4);
                Array.Copy(key2, 0, all, 4, 4);
                Array.Copy(lastKey, 0, all, 8, 8);

                //Finish
                byte[] handshakeAnswer = MD5.Create().ComputeHash(all);
                string handshake = "HTTP/1.1 101 Web Socket Protocol Handshake" + Environment.NewLine;
                handshake += "Upgrade: WebSocket" + Environment.NewLine;
                handshake += "Connection: Upgrade" + Environment.NewLine;
                handshake += "Sec-WebSocket-Origin: " + origin + Environment.NewLine;
                handshake += "Sec-WebSocket-Location: " + location + Environment.NewLine;
                handshake += Environment.NewLine;

                byte[] handshakeBytes = new byte[Encoding.ASCII.GetByteCount(handshake) + 16];
                Array.Copy(Encoding.UTF8.GetBytes(handshake), handshakeBytes, handshake.Length);
                Array.Copy(handshakeAnswer, 0, handshakeBytes, handshake.Length, 16);
                LogOut("Handshake");
                stream.Write(handshakeBytes, 0, handshakeBytes.Length);

                User u = new User(client, Recieve, Remove);
                _users.Add(u);
            }
            catch
            {
                client.Close();
            }
            finally
            {
                activeThreads.Remove(Thread.CurrentThread);
            }
        }

        //запуск сервера
        public void Start()
        {
            listener = new TcpListener(_ip, _port);
            activeThreads = new List<Thread>();
            listener.Start();
            Thread processConnectionsThread = new Thread(ProcessConnections);
            processConnectionsThread.Start();
            activeThreads.Add(processConnectionsThread);
        }

        //остановка сервера
        public void Stop()
        {
            foreach (Thread t in activeThreads)
                t.Abort();
            for (int i = 0; i < _users.Count; i++)
                _users[i].Stop();
            listener.Stop();
        }

        private void ProcessConnections()
        {
            while (true)
            {
                TcpClient client = listener.AcceptTcpClient();
                Thread handshakeThread = new Thread(Handshake);
                handshakeThread.Start(client);
                activeThreads.Add(handshakeThread);
            }
        }
		
		//user отключился
        //<***************************************
		//*** Server -> Client ***
		//{type: ChatUserDisconnect, login: nickname}
		//***************************************>
		private void Remove(User user)
        {
            if (_users.Contains(user))
            {
				PackageChatAuth PCAobj = new PackageChatAuth();
				PCAobj.type = "ChatUserDisconnect";
				PCAobj.login = user.Nick;
				string PCAjson = JsonConvert.SerializeObject(PCAobj);
				SendAll(PCAjson);
                _users.Remove(user);
                LogIn(String.Format("User {0} disconnected", user.Nick));
                //SendAll("\"" + user.Nick + "\" Отключился");
            }
        }
		
		// игра отключена
        //<***************************************
		//*** Server -> Client ***
		//{type: Action, action: Remove_game, name: gamename}
		//***************************************>
		private void RemoveGame(KoAbra game)
        {
            if (_koabras.Contains(game))
            {
				PackageRemoveGame PRGobj = new PackageRemoveGame();
				PRGobj.type = "Action";
				PRGobj.action = "Remove_game";
				PRGobj.name = game.name;
				string PRGjson = JsonConvert.SerializeObject(PRGobj);
				SendAll(PRGjson);
                _koabras.Remove(game);
                LogIn(String.Format("Game {0} disconnected", game.name));
            }
        }

        //отправка сообения всем клиентам
        private void SendAll(string message)
        {
            for(int i = 0; i < _users.Count; i++)
               if(_users[i].Alive)
                   _users[i].SendMessage(String.Format("Send all: {1}", message));
           // Users.RemoveAll(user => user.Alive == false);
        }
		
		//отправка сообения клинету, приславшему сообщение
        private void SendThis(User user, string message)
        {
			user.SendMessage(String.Format("Send to {1}: {2}", user.Nick, message));
        }

        //процесс приема сообщений
        private void Recieve(User user, string message)
        {
			// если user аутентифинирован
			
            if (user.Authentificated)
            {
                LogIn(String.Format("Message Recieved (from {0}): {1}", user.Nick, message));
                
				//<***************************************
				//=== Обработка сообщений из чата ===
				//*** Client -> Server ***
				//{type: ChatMessage, message: содержимое сообщения}
				//*** Server -> Client ***
				//{type: ChatMessage, login: nickname, message: содержимое сообщения}
				//***************************************>			
				JObject jo = JObject.Parse(message.ToString());
				if((string)jo["type"] == "ChatMessage")
				{
					PackageChatMessage PCMobj = new PackageChatMessage();
					PCMobj.login = user.Nick;
					PCMobj.message = (string)jo["message"];
					LogIn(message);
					
					string PCMjson = JsonConvert.SerializeObject(PCMobj);
					SendAll(PCMjson);
				}
				//***************************************>
				//SendAll(String.Format("\"{0}\": {1}", user.Nick, message.Replace("\n", "")));
				
				//<***************************************
				//=== Обработка сообщений типа Action ===							
				if((string)jo["type"] == "Action")
				{
					//<***************************************
					//===Пользователь желает создать игру===
					//*** Client -> Server ***
					//{"type": "Action", "action": "Create_game", "name": "mapname"}
					//*** Server -> Client ***
					//{"type": "Action", "action": "Create_game", "status": "OK", "reason": "0"}
					//{"type": "Action", "action": "Create_game", "status": "ERR", "reason": "номер_причины"}
					if((string)jo["action"] == "Create_game")
					{
						KoAbra game = new KoAbra(user, (string)jo["name"]);		// создаем новую игру
						game._game_users.Add(user);			// добавляем создателя к списку игроков
						_koabras.Add(game);					// добавляем созданную игру в список игр
						
						PackageResult PRobj = new PackageResult();
						PRobj.action = "Create_game";
						PRobj.status = "OK";
						PRobj.reason = "0";
						string PRjson = JsonConvert.SerializeObject(PRobj);
						
						SendThis(user, PRjson);
					}
					//***************************************>
					
					//<***************************************
					//===Пользователь желает получить список игр===
					//*** Client -> Server ***
					//{"type": "Action", "action": "Get_gamelist"}
					//*** Server -> Client ***
					//{"type":"Action","action":"Send_gamelist","names":["gamename","gamename2"]}
					
					if((string)jo["action"] == "Get_gamelist")
					{						
						PackageListNames PLNobj = new PackageListNames(_koabras.Count);
						int i = 0;
						foreach(KoAbra game in _koabras)
						{
							PLNobj.names[i] = game.name;
							i++;
						}
						
						string json = JsonConvert.SerializeObject(PLNobj);
						
						SendThis(user, json);
					}
					
					//***************************************>
										
					//<***************************************
					//=== Получение списка карт ===
					//*** Client -> Server ***
					//{"type":"Action","action":"Get_maplist"}
					//*** Server -> Client ***
					//{"type": "Action", "action": "Send_maplist", "maps": ["name1", "name2", "name3"]}
					if((string)jo["action"] == "Get_maplist")
					{
						string path = @"Maps\maps.list";
						
						using (StreamReader sr = File.OpenText(path)) 
				        {
				            string s = "";
							string buf = "";
				            while ((buf = sr.ReadLine()) != null) 
				            {
				                s += buf;
				            }
							SendThis(user, s);
							LogOut(s);
				        }						
					}
					//***************************************>
					
					//<***************************************
					//===Пользователь желает присоединиться к игре===
					//*** Client -> Server ***
					//{"type": "Action", "action": "Join_game", "name": "gamename"}
					//*** Server -> Client ***
					//{"type": "Action", "action": "Join_game", "status": "OK", "reason": "0"}
					//{"type": "Action", "action": "Join_game", "status": "ERR", "reason": "номер_причины"}
					//reason:
					//1 - данной игры не существует (создатель успел выйти прежде, чем пользователь соединился)
					//2 - ???
					if((string)jo["action"] == "Join_game")
					{
						int i = 0;
						bool found = false;
						foreach(KoAbra game in _koabras)
						{
							if(game.name == (string)jo["name"])
							{
								_koabras[i]._game_users.Add(user);
								found = true;
								break;
							}
							i++;
						}
						string mess;
						if(found)
						{
							mess = "{\"type\": \"Action\", \"action\": \"Join_game\", \"status\": \"OK\", \"reason\": \"0\"}";
						}
						else
						{
							mess = "{\"type\": \"Action\", \"action\": \"Join_game\", \"status\": \"OK\", \"reason\": \"1\"}";							
						}
						LogOut(mess);
						SendThis(user, mess);	
					}
					//***************************************>
					
					//<***************************************
					//===Получить список игроков в текущей игре===
					//*** Client -> Server ***
					//{"type": "Action", "action": "Get_all_players", "gamename": "name"}
					//*** Server -> Client ***
					//{"type": "Action", "action": "Send_all_players", "names": ["player1", "player2", "player3"]}
					/*if((string)jo["action"] == "Get_all_players")
					{
						int i = 0;
						foreach(KoAbra game in _koabras)
						{
							if(game.name == (string)jo["gamename"])
							{
								PackageListNames PLNobj = new PackageListNames(_koabras[i]._game_users.Count);
								for(int j = 0; j < _koabras[i]._game_users.Count; j++)
								{
									PLNobj.action = "Get_all_players";
									PLNobj.names[j] = _koabras[i]._game_users[j].Nick;
								}
								string json = JsonConvert.SerializeObject(PLNobj);						
								SendThis(user, json);
							}
							i++;
						}
					}*/
					//***************************************>
				}
				//***************************************>
				
				//<***************************************
				//*** проверка чтения карты из файла ***
				if((string)jo["message"] == "!Read")
				{
					string path = @"Maps\default.map";
					
					// Open the file to read from.
	        		using (StreamReader sr = File.OpenText(path)) 
			        {
			            string s = "";
						string buf = "";
			            while ((buf = sr.ReadLine()) != null) 
			            {
			                s += buf;
			            }
						Console.WriteLine(s);
						JObject jobj = JObject.Parse(s);
						JArray jarr = (JArray)jobj["locality"];
						Console.WriteLine((string)jarr[1][1]+" "+(string)jobj["name"]);
			        }
				}
				//***************************************>
				
            }
			// иначе, аутентифицироваться
            else
            {	
				//<***************************************
				//*** Client -> Server ***
				//{type: ChatMessage, message: содержимое сообщения}
				//в случае авторизации приходит пакет {type: ChatMessage, message: nickname}
				JObject jobj = JObject.Parse(message);
				user.Nick = (string)jobj["message"];
				//***************************************>
                //user.Nick = message.Replace("\"", "");
                user.Authentificated = true;
                LogIn(String.Format("User Connected: {0}", user.Nick));
				LogIn(message);
                
				//<***************************************
				//*** Server -> Client ***
				// {type: ChatAuth, login: nickname}
				PackageChatAuth PCAobj = new PackageChatAuth();
				PCAobj.login = user.Nick;
				string PCAjson = JsonConvert.SerializeObject(PCAobj);
				SendAll(PCAjson);
				LogIn("!!!!!!!");
				//***************************************>
				//SendAll(String.Format("\"{0}\" подключился", PAobj.login));
            }
        }

        private void LogIn(string message)
        {
            if (Log)
                Console.WriteLine(string.Format("{0} > {1}", DateTime.Now.ToLongTimeString(), message));
        }

        private void LogOut(string message)
        {
            if (Log)
                Console.WriteLine(string.Format("{0} < {1}", DateTime.Now.ToLongTimeString(), message));
        }
    }
}
